package com.vdimitr.happycube.puzzle;

import java.util.Arrays;

import com.vdimitr.happycube.puzzle.piece.PuzzlePieceColor;
import com.vdimitr.happycube.puzzle.solution.output.IPuzzlePieceSolutionOutput;
import com.vdimitr.happycube.puzzle.solution.output.PuzzlePieceSolutionToStringOutput;
import com.vdimitr.happycube.puzzle.solution.output.conversion.PuzzlePieceSolutionMatrixToStringConverter;
import com.vdimitr.happycube.puzzle.solution.output.translation.IPuzzlePieceMatrixValueTranslator;
import com.vdimitr.happycube.puzzle.solution.output.translation.PuzzlePieceMatrixValueZeroOneTranslator;
import com.vdimitr.happycube.puzzle.solution.output.writer.PuzzlePieceSolutionFileWriter;
import com.vdimitr.happycube.puzzle.solution.output.writer.IPuzzlePieceSolutionOutputWriter;

/**
 * This class is the main entry point to the happy cube solution project.
 * Running its main method with the correct arguments can produce all possible
 * solutions for a cube of specified color and write each one to a single file
 * under a specified folder.
 * 
 * @author vasileios.dimitriadis
 * 
 */
public class CubeSolution {

	/**
	 * Input argument for not generating any file output
	 */
	public static final String NO_OUTPUT = "-noouput";

	/**
	 * Input argument for calculating only one solution
	 */
	public static final String ONE_SOLUTION = "-one";

	/**
	 * The number of minimum arguments needed to run main
	 */
	private static final int MINIMUM_ARGUMENTS = 2;

	/**
	 * Default constructor. No need to instantiate.
	 */
	private CubeSolution() {
	}

	/**
	 * Finds one or more solutions to happy cubes of specific color and can
	 * export those solutions to a folder. </br> It runs with minimum 2
	 * arguments: </br> First argument: color of the cube. Currently supported
	 * colors are:
	 * <ul>
	 * <li>BLUE
	 * <li>RED
	 * <li>PURPLE
	 * <li>YELLOW
	 * </ul>
	 * Second argument: output configuration.
	 * <ul>
	 * <li>either a path to an existing folder when the solutions will be
	 * exported to files
	 * <li>or '-noouput' when there is no need for export
	 * </ul>
	 * Third argument (optional): by giving '-one' as an argument the generated
	 * solutions will be limited to 1.
	 * 
	 * @param args
	 *            [BLUE|RED|PURPLE|YELLOW] [output folder|'-noouput'] ['-one']
	 */
	public static void main(String[] args) {
		checkArguments(args);

		String colorName = args[0];

		System.out.println("Trying variations for cube: " + colorName);
		System.out.println("Plz wait...");

		CubeSolver solver = configureCubeSolver(args);
		solver.solve(PuzzlePieceColor.valueOf(colorName.toUpperCase()));

		System.out.println("Tried Variations: " + solver.getAttemptCounter());
		System.out.println("Solutions Found: " + solver.getSolutionCoutner());
	}

	/**
	 * Checks for the minimum required arguments for correct execution. If the
	 * check fails then an {@link IllegalArgumentException} is thrown.
	 * 
	 * @param args
	 *            minimum arguments of cube color and output folder flag
	 */
	private static void checkArguments(String args[]) {
		if (args == null || args.length < MINIMUM_ARGUMENTS) {
			throw new IllegalArgumentException(
					"Must set at least color and output folder");
		}
		checkColorName(args[0]);
		checkOutputFolderName(args[1]);
	}

	/**
	 * Checks for not null input folder name. If null then an
	 * {@link IllegalArgumentException} is thrown.
	 * 
	 * @param outputFolderName
	 *            Must be not null.
	 */
	private static void checkOutputFolderName(String outputFolderName) {
		if (outputFolderName == null) {
			throw new IllegalArgumentException(
					"Must set an output folder name or " + NO_OUTPUT);
		}
	}

	/**
	 * Checks that the color of the cube specified is valid. If not then an
	 * {@link IllegalArgumentException} is thrown.
	 * 
	 * @param colorName
	 *            Must be not null and one of [BLUE|RED|PURPLE|YELLOW]
	 */
	private static void checkColorName(String colorName) {
		// no null is allowed
		if (colorName == null) {
			throw new IllegalArgumentException("Must provide color name");
		}

		boolean colorNameFound = false;

		// find color
		for (PuzzlePieceColor color : PuzzlePieceColor.values()) {
			if (color.name().equalsIgnoreCase(colorName)) {
				if (color.equals(PuzzlePieceColor.NULL)) {
					continue; // you tried to trick me ;-)
				}
				colorNameFound = true;
			}
		}

		if (!colorNameFound) {
			throw new IllegalArgumentException("Color '" + colorName
					+ "' not valid. Must be one of "
					+ Arrays.toString(PuzzlePieceColor.values()));
		}
	}

	/**
	 * Configures {@link Cubesolver} based on the input arguments for output and
	 * whether one or more solutions should be calculated.
	 * 
	 * @param args
	 *            configuration arguments (output and one or more solutions
	 *            flag)
	 * @return a configured instance of {@link Cubesolver} based on execution
	 *         arguments
	 */
	private static CubeSolver configureCubeSolver(String args[]) {
		CubeSolver solver = new CubeSolver();
		solver.setOutput(configureOuput(args));
		solver.setOnlyOneSolution(isOneSolutionFlagSet(args));
		return solver;
	}

	/**
	 * Constructs the output mechanism for exporting solutions found. (Something
	 * like IoC).
	 * 
	 * @param args
	 *            Must have color of cube and output folder flag
	 * @return null if no output is needed or a configured output which exports
	 *         a cube solution to a file.
	 */
	private static IPuzzlePieceSolutionOutput configureOuput(String args[]) {
		String colorName = args[0];
		String outputFolderName = args[1];

		if (NO_OUTPUT.equalsIgnoreCase(outputFolderName)) {
			return null;
		} else {
			// set up the translator of values in the puzzle piece matrix
			IPuzzlePieceMatrixValueTranslator translator = new PuzzlePieceMatrixValueZeroOneTranslator();

			// set up the converter for transforming a matrix into string
			PuzzlePieceSolutionMatrixToStringConverter converter = new PuzzlePieceSolutionMatrixToStringConverter(
					translator);

			// set up the output
			IPuzzlePieceSolutionOutputWriter writer = new PuzzlePieceSolutionFileWriter(
					outputFolderName, colorName);

			return new PuzzlePieceSolutionToStringOutput(converter, writer);
		}
	}

	/**
	 * Checks if one or more solutions should be generated
	 * 
	 * @param args
	 *            the arguments passed
	 * @return true if the flag is set in the input arguments
	 */
	private static boolean isOneSolutionFlagSet(String[] args) {
		if (args.length > MINIMUM_ARGUMENTS) {
			for (int i = 0; i < args.length; i++) {
				String solutionFlag = args[i];
				if (ONE_SOLUTION.equalsIgnoreCase(solutionFlag)) {
					return true;
				}
			}
		}
		return false;
	}

}
